'''
* 文件名称: AppBuild.py
* 内容摘要: 编译 app 下的工程文件
* 创建者の: 孔佳伟
* 个人主页: https://gitee.com/thin-wind/jump
* 修改记录: 
'''

import os
import json

OUTPUT      = "../output/app"

AENTRY_O    = "aentry.o"

APP_OUT     = "app.out"
APP_BIN     = "app.bin"


ROOT = os.getcwd()          # 获取当前所在路径的绝对路径作为工程根目录
BUILD_CFG = "BUILD.json"    # 编译配置文件名
project = {}                # 通过递归遍历编译配置文件 BUILD_CFG 后所得信息放入 project 字典中


# 该函数通过递归遍历路径 path 下的所有的 BUILD.json 文件，
# 并将遍历后的结果以 '源文件文件名':['源文件所在路径', ['源文件所包含的头文件路径1', '源文件所包含的头文件路径1']] 
# 这种形式的数据添加到字典 project 中
def Parse_BUILD_CFG(path):
    JsonPathName = os.path.join(path, BUILD_CFG)
    # 以只读方式打开文件 JsonPathName
    with open(JsonPathName, 'r') as f:
        # 读取文件内容到 json_text  
        json_text = f.read()
        # 将 json_text 内容转为 python 字典 json_dict
        json_dict = json.loads(json_text)
        # 遍历字典 json_dict
        for key in json_dict:
            if key == 'src':    # 如果是源文件，则将 '源文件文件名':['源文件所在路径', ['源文件所包含的头文件路径1', '源文件所包含的头文件路径1']] 这种形式的数据添加到字典 project 中
                if json_dict[key]:
                    for item in json_dict[key]:
                        project[item] = [path, json_dict['inc']]
                        
            elif key == 'dir':  # 如果是文件夹，则进入该文件夹内，递归调用 Parse_BUILD_CFG 
                if json_dict[key]:
                    for item in json_dict[key]:
                        new_path = os.path.join(path, item)
                        Parse_BUILD_CFG(new_path)
            elif key == 'inc': # 如果是头文件路径，则不处理
                pass
            
            else:
                print("Invalid key")

    return project



# 编译 app
def compile_app(user_src):
    # 遍历 kernel 源文件
    for key in user_src:
        if key[-2:] == '.c':
            # 切换到 .c 源文件所在的目录
            cmd_str = os.path.join(ROOT, user_src[key][0])
            print('Enter: ' + cmd_str)
            os.chdir(cmd_str)

            # 获取包含头文件路径
            include = ''
            for item in user_src[key][1]:
                tmp = ' -I' + os.path.join(ROOT, item)
                include += tmp
            
            # 编译 .c 文件
            # 例：gcc -m32 -Iinclude -c main.c -o main.o
            src = key
            obj = key[0:-2] + '.o'
            out = os.path.join(ROOT, OUTPUT, obj)
            cmd_str = 'gcc -m32 -nostdinc -fno-stack-protector' + include + " -c " + src + " -o " + out
            print(cmd_str)
            os.system(cmd_str)

        elif key[-4:] == '.asm':
            # 切换到 .asm 源文件所在的目录
            cmd_str = os.path.join(ROOT, user_src[key][0])
            print('Enter: ' + cmd_str)
            os.chdir(cmd_str)

            # 编译 .asm 文件
            src = key
            obj = key[0:-4] + '.o'
            out = os.path.join(ROOT, OUTPUT, obj)
            cmd_str = "nasm -f elf32 " + src + " -o " + out
            print(cmd_str)
            os.system(cmd_str)

        else:
            print(key + " is unknow.")

# 链接 app obj 临时文件
def ld_app_obj(app_out):
    # 遍历 .o 文件
    obj = ''
    for item in os.listdir(os.path.join(ROOT, OUTPUT)):
        # 排除 AENTRY_O，它是 app 程序的入口，链接时需要放在最前面。其它 .o 文件链接时不考虑先后顺序
        if item[-2:] == '.o' and item != AENTRY_O:
            tmp = ' ' + item
            obj += tmp
     # 链接 kernel
    src = AENTRY_O + obj
    out = app_out
    cmd_str = "ld -Ttext 0x80000 -s -m elf_i386 -o " + out + " " + src
    print(cmd_str)
    os.system(cmd_str)

# 提取 elf 程序中的 bin 有效数据
def elf_2_bin(elf, bin):
    # 将 app.out 转为 app.bin
    src = elf
    out = bin
    cmd_str = "objcopy -O binary " + src + " " + out
    print(cmd_str)
    os.system(cmd_str)


# 打印 project
def print_project(project):
    for item in project:
        str_print = item + ': ' + str(project[item])
        print(str_print)

def main():
    # 路径为空即表示当前所在目录
    root_path = ''
    project = Parse_BUILD_CFG(root_path)
    
    # 打印 project
    # print_project(project)

    # 创建文件夹 output
    if not os.path.exists(OUTPUT):
        os.makedirs(OUTPUT)

    # 编译 app
    compile_app(project)

    # 切换到 OUTPUT 目录下
    cmd_str = os.path.join(ROOT, OUTPUT)
    print('Enter: ' + cmd_str)
    os.chdir(cmd_str)

    # 链接 app obj 临时文件
    ld_app_obj(APP_OUT)

    # 提取 elf 程序中的 bin 有效数据
    elf_2_bin(APP_OUT, APP_BIN)


    print("App compiled successfully")


if __name__ == '__main__':
    main()